package genome;

import java.io.BufferedReader;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.LinkedList;
import static genome.ParametersRandomGen.*;
import exceptions.FunctionExpressionException;
import exceptions.RuleFormatException;
import polylegs.Node;
import random.RandomDistrib;

public class Genome {

	LinkedList<Rule> rules = new LinkedList<Rule>();
	LinkedList<Constant> constants = new LinkedList<Constant>();
	LinkedList<Function> functions = new LinkedList<Function>();

	int[] genome;// will be used if the decomposition into objects is too heavy
	boolean tooheavy;
 
	String genometype;
	boolean useEnvironment;

	public Genome(String rulesFile) {

		try {
			BufferedReader br = new BufferedReader(new FileReader(new File(
					rulesFile)));

			String currentLine = br.readLine();

			currentLine = br.readLine();

			while (!currentLine.startsWith("//")) {

				constants.addLast(new Constant(currentLine));
				currentLine = br.readLine();
			}

			currentLine = br.readLine();
			while (!currentLine.startsWith("//")) {
				Function function = new Function(currentLine);
				functions.addLast(function);
				currentLine = br.readLine();
			}

			currentLine = br.readLine();
			while (!currentLine.startsWith("//")) {
				Rule rule = new Rule(currentLine);
				rules.addLast(rule);
				currentLine = br.readLine();
			}

			br.close();

		} catch (FileNotFoundException e) {
			System.out.println("rules file " + rulesFile + " not found");
			e.printStackTrace();
		} catch (IOException e) {
			System.out.println("parse error");
			e.printStackTrace();
		}
	}

	public Genome(String ruleType, boolean env) {
		// init a random genome
		genometype = ruleType;
		useEnvironment = env;
		// random init of the genome
		// the distribution of teh number of rules,constants, fucntions
		// follows a gamma distribution with shape 2.0 and mean nb_mean
		int nb_cst = (int) Math.ceil(RandomDistrib.sampleGamma(2,nb_cst_mean / 2));
		int nb_functions = (int) Math.ceil(RandomDistrib.sampleGamma(2,nb_functions_mean / 2));
		int nb_rules = (int) Math.ceil(RandomDistrib.sampleGamma(2,nb_rules_mean / 2));

		addConstant0();
		for (int i = 0; i < nb_cst; i++) {
			addConstant();
		}
		for (int i = 0; i < nb_functions; i++) {
			addFunction();
		}
		for (int i = 0; i < nb_rules; i++) {
			addRule();
		}
	}

	public String toString() {
		String result = "";
		result += "//constants\n";
		for (Constant constant : constants) {
			result += constant.toString() + ";\n";
		}
		result += "//functions\n";
		for (Function function : functions) {
			result += function.toString();
		}
		result += "//rules\n";
		for (Rule rule : rules) {
			result += rule.toString();
		}
		result += "//end";

		return result;
	}

	public void write(String output) {
		try {
			FileWriter fw = new FileWriter(new File(output));
			fw.write(toString());
			fw.close();
		} catch (IOException e) {
			System.out.println("output file for genome not found " + output);
			e.printStackTrace();
		}
	}

	public void applyTo(Node n) {

		LinkedList<Integer> variables = new LinkedList<Integer>();

		for (Function f : functions) {
			try {
				variables.addLast(f.computeFunction(n, constants));
			} catch (FunctionExpressionException e) {
				variables.addLast(null);
			}
		}

		for (Rule r : rules) {
			r.apply_rule(n, variables, constants);
		}

	}

	private void addConstant() {
		constants.addLast(new Constant());
	}

	private void addConstant0() {
		constants.addLast(new Constant(0));
	}

	private void addFunction() {
		functions.addLast(new Function(constants));

	}

	private void addRule() {
		try {
			rules.add(new Rule(constants, functions, genometype,useEnvironment));
		} catch (RuleFormatException e) {
		}
	}

	public void mutate() {
		switch (RandomDistrib.resultat(ParametersRandomGen.proba_mutation_type)) {
		case 0:
			augmentGenome();
			break;
		case 1:
			reduceGenome();
			break;
		case 2:
			mutateGenome();
			break;
		case 3:
			copyGenome();
			break;
		case 4:
			modifyOrderGenome();
			break;
		}
	}

	private void augmentGenome() {

		// add exactly N constants, functions, rules , those lines are added
		// after the current lines
		// variables and functions will not be used

		for (int i = 0; i < nb_cst_max; i++) {
			if (Math.random() * nb_max < average_number_of_addings) {
				addConstant();
			}
		}
		for (int i = 0; i < nb_functions_max; i++) {
			if (Math.random() * nb_max < average_number_of_addings) {
				addFunction();
			}
		}
		for (int i = 0; i < nb_rules_max; i++) {
			if (Math.random() * nb_max < average_number_of_addings) {
				addRule();
			}
		}

	}

	private void reduceGenome() {
		// remove in mean N constants, functions or rules
		// 0 can not be removed
		// for each line, there is a porbability that this line is removed
		// removing a constant will shift the other constants :
		// if we remove constant2, constant3 will become constant2

		int nb_of_genomic_lines = constants.size() + functions.size()
				+ rules.size() - 1;
		for (int i = 1; i < constants.size(); i++) {
			if (Math.random() * nb_of_genomic_lines < average_number_of_removings) {
				constants.remove(i);
			}
		}
		for (int i = 0; i < functions.size(); i++) {
			if (Math.random() * nb_of_genomic_lines < average_number_of_removings) {
				functions.remove(i);
			}
		}
		for (int i = 0; i < rules.size(); i++) {
			if (Math.random() * nb_of_genomic_lines < average_number_of_removings) {
				rules.remove(i);
			}
		}

	}

	private void copyGenome() {
		// copy in mean N constants, functions or rules
		// for each line, there is a probability that this line is copied
		// copy a constant will shift the other constants :
		// if we copy constant2,new constant becomes constant3, constant3 will
		// become constant4
		// we can copy 0

		int nb_of_genomic_lines = constants.size() + functions.size()
				+ rules.size();
		for (int i = 0; i < constants.size(); i++) {
			if (Math.random() * nb_of_genomic_lines < average_number_of_copies) {
				constants.add(i, constants.get(i));
			}
		}
		for (int i = 0; i < functions.size(); i++) {
			if (Math.random() * nb_of_genomic_lines < average_number_of_copies) {
				functions.add(i, functions.get(i));
			}
		}
		for (int i = 0; i < rules.size(); i++) {
			if (Math.random() * nb_of_genomic_lines < average_number_of_copies) {
				rules.add(i, rules.get(i));
			}
		}

	}

	private void mutateGenome() {
		// mutate in mean N constants, functions or rules
		// 0 can not be mutated
		// for each line, there is a probability that this line is mutated

		int nb_of_genomic_lines = constants.size() + functions.size()
				+ rules.size() - 1;
		for (int i = 1; i < constants.size(); i++) {
			if (Math.random() * nb_of_genomic_lines < average_number_of_changes) {
				constants.get(i).mutate();
			}
		}
		for (Function f : functions) {
			if (Math.random() * nb_of_genomic_lines < average_number_of_changes) {
				f.mutateFunction(constants);
			}
		}
		for (Rule r : rules) {
			if (Math.random() * nb_of_genomic_lines < average_number_of_changes) {
				try {
					r.mutateRule(constants, functions);
				} catch (RuleFormatException e) {
				}
			}
		}
	}

	private void modifyOrderGenome() {

		// each line has a certain probability to be siwtched with teh next one
		int nb_of_genomic_lines = constants.size() + functions.size()
				+ rules.size() - 1;
		for (int i = 1; i < constants.size() - 1; i++) {
			if (Math.random() * nb_of_genomic_lines < average_number_of_order_changes) {

				constants.add(i + 1, constants.remove(i));
			}
		}
		for (int i = 0; i < functions.size() - 1; i++) {
			if (Math.random() * nb_of_genomic_lines < average_number_of_order_changes) {
				functions.add(i + 1, functions.remove(i));
			}
		}
		for (int i = 0; i < rules.size() - 1; i++) {
			if (Math.random() * nb_of_genomic_lines < average_number_of_order_changes) {
				rules.add(i + 1, rules.remove(i));
			}
		}

	}

}
